// vim: foldmethod=marker
/**
 * TODO:
 * - タグの入力補完機能
 *   - MTはタブ押下で補完していたのでその調査 -> L560位から
 *   - 補完に関するキー操作をまとめる
 * - 検索キーワード押下時Enterキー押下で検索可能にする
 * - ブクマ編集
 * - 1行も検索にヒットしなかった場合の処理
 * - 元ページの要素が重なることがある
 * - frameを使ったページ等で動かなくなることがあるので調査
 *   - 対処しようがなかったら、登録用の別ページを作るかも
 * - 元ページが重い場合に、すぐブクマを押すとJSエラーになることへの対処
 * - 登録後に、今登録したタグが更新されないでTopに戻ることがある。
 * - FireFoxで、ブクマを押して閉じると、ずっとページがリロード中の表示になる
 * - #!/usr/bin/env ruby に変更
 * - DLできる形にする
 * - 適切なイベントのdetatchを行う。innerHTML = "" とやってるところとか
 * - デザイン
 *
 * SUSPENDED:
 * - Obm.showが行き当たりばったりなので作り変える
 * - タグに半角英数字で長い文字列があると、タグ一覧divで横にスクロールバーが出てしまう
 * - ボタンとして使用している文字列をWクリックしたときに、選択状態にならないようにする
 * - 一度でもログイン認証が通ったら、次からはリクエストにid/pwを含めない処理
 * - api.cgiで、CGI.newしてから再度api.rbでCGI.newをしているのが気になる
 * - target→name属性による同画面内でのページ移動をした際に、"#" 以降の文字列が document.URL に入らず
 *   結果、# の無いURLとして登録処理が行われる。# 付きURLに直接遷移している場合は問題ない。
 *
 * GIVE UP:
 * - iframeの幅がウィンドウ一杯なので、そこに位置する裏のページを触れなくなる
 *   -> iframeのサイズ固定以外は無理、小さく固定するのはUI的に良くないので無理。
 *   -> IEだと、裏のページが触れる
 * - ブクマで開閉する際のアニメーション
 *   -> ブクマで開閉しているのは、権限の問題でiframe要素を操作できないから。つまり透過されたiframeは残る。
 *      ブクマ開閉だと、逆にiframe内部を操作できないので、アニメーションは難しい。
 * - Safari2(MacOSX)対応
 *
 * @author kjirou <kjirou.web[at-mark]gmail.com>
 *                <http://kjirou.sakura.ne.jp/mt/>
 */
$c = ("console" in window)? console.log: function(e){return ""};

// {{{ Obm
/**
 * 俺BookMarkクラス
 */
var Obm = function(){

    var tp = Obm.TopPage.singleton();
    tp.generate();
    tp.hide();
    document.body.appendChild(tp.frame());

    var rp = Obm.RegisterPage.singleton();
    rp.generate();
    rp.hide();
    document.body.appendChild(rp.frame());

    var bl = Obm.BookmarksPage.singleton();
    bl.generate();
    bl.hide();
    document.body.appendChild(bl.frame());

    Obm.APIConnector.singleton();

    Obm.show();
};

/**
 * パス・URLの設定
 */
Obm.BASE_DIR = "{*$WWW_BASE_DIR*}";
Obm.JS_DIR = Obm.BASE_DIR + "js/";
Obm.API_URL = Obm.BASE_DIR + "api.cgi";

/**
 * ブラウザ種類を示すフラグ
 *
 * @desc XP-IE6 Vista-IE7 XP-FireFox Mac-Safari のみ対応
 * @see http://www.fureai.or.jp/~tato/DHTML/simple/part4/tips/browschk.htm
 * @var num
 */
Obm.browser = (function() {

    // XP-IE6
    if (navigator.userAgent.match(/MSIE 6/) !== null) {
        return 1;
    // Vista-IE7
    } else if (navigator.userAgent.match(/MSIE 7/) !== null) {
        return 2;
    // Mac-Safari
    } else if (navigator.userAgent.match(/Safari/) !== null) {
        return 3;
    // XP-FireFox
    } else if (navigator.userAgent.match(/Gecko/) !== null) {
        return 4;
    };
    // other
    return 0;
})();

Obm.isIE = (function() {
    return (Obm.browser === 1 || Obm.browser === 2)? true: false;
})();

Obm.requestParams = (function(){
    var pStrings = document.URL.replace(/^[^?]+\?/, "").split(/&/);
    var params = {};
    for (var i = 0; i < pStrings.length; i++) {
        var pair = pStrings[i].split(/=/);
        params[pair[0]] = (pair[1] !== undefined)? decodeURIComponent(pair[1]): "";
    };
    return params;
})();

/**
 * @see prototype.js::Object.extend
 */
Obm.extend = function(destination, source) {
    for (var property in source) {
        destination[property] = source[property];
    }
    return destination;
};

/**
 * @see prototype.js::Object.clone
 */
Obm.clone = function(object) {
    return Obm.extend({}, object);
};

/**
 * @see prototype.js::$
 */
Obm.$ = function(element) {
    if (typeof element == 'string')
        element = document.getElementById(element);
    return element;
};

/**
 * @see prototype.js::Event.observe
 */
Obm.observeEvent = function(element, name, observer, useCapture) {
    var element = Obm.$(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
            name = 'keydown';

    if (element.addEventListener) {
        element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
        element.attachEvent('on' + name, observer);
    };
};

/**
 * @see prototype.js::String.escapeHTML
 */
Obm.escapeHTML = function(str) {
    var div = document.createElement('div');
    var text = document.createTextNode(str);
    div.appendChild(text);
    return div.innerHTML;
};

/**
 * @see prototype.js::Element.getStyle
 * @desc 必要な部分のみ移しているので注意
 */
Obm.getStyle = function(element, style){
    element = Obm.$(element);
    var value = element.style[style];
    if (!value) {
        if (document.defaultView && document.defaultView.getComputedStyle) {
            var css = document.defaultView.getComputedStyle(element, null);
            value = css ? css[style] : null;
        } else if (element.currentStyle) {
            value = element.currentStyle[style];
        };
    };
    return value == 'auto' ? null : value;
};

Obm.inArray = function(needle, haystack){
    for (var i = 0; i < haystack.length; i++) {
        if (needle === haystack[i]) return true;
    };
    return false;
};

Obm.makeApplyNew = function(){
    return function(args){
        var Scope = function(){};
        Scope.prototype = this.prototype;
        var scope = new Scope();
        var instance = this.apply(scope, args || []);
        return (instance instanceof Object) ? instance : scope;
    };
};

Obm.makeSingleton = function(){
    return function(){
        if ("__obm_instance__" in this === false)
            this.__obm_instance__ = this.applyNew(arguments);
        return this.__obm_instance__;
    };
};

Obm.show = function(){
    var tp = Obm.TopPage.singleton();
    tp.frame().style.top = "-305px";
    Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {api_name: "get_tags"});
    tp.show();
    Obm.move(Obm.TopPage.singleton().frame(), [5, 5], 20, 0.2);

    var rp = Obm.RegisterPage.singleton();
    rp.hide();
    rp.frame().style.top = "5px";

    Obm.BookmarksPage.singleton().hide();
};

/**
 * 要素をアニメーションで移動する
 *
 * @param obj   element HTML要素オブジェクト
 * @param array end     [0]=終点のtop座標, [1]=終点のleft座標
 * @param num   delay   アニメーションの間隔 msで指定
 * @param num   mod     移動距離算出用係数、残りの要移動距離にこの係数を掛けただけ移動する
 */
Obm.move = function(element, end, delay, mod){

    var currentY = parseInt(Obm.getStyle(element, "top"));
    var currentX = parseInt(Obm.getStyle(element, "left"));
    var remainsY = parseInt(end[0]) - parseInt(Obm.getStyle(element, "top"));
    var remainsX = parseInt(end[1]) - parseInt(Obm.getStyle(element, "left"));
    var calc = function(remains, mod){
        if (remains < 0) return Math.floor(remains * mod);
        return Math.ceil(remains * mod);
    };

    var timerId = setInterval(function(){

        var deltaY = calc(remainsY, mod);
        currentY = currentY + deltaY;
        remainsY = remainsY - deltaY;
        element.style.top = currentY + "px";

        var deltaX = calc(remainsX, mod);
        currentX = currentX + deltaX;
        remainsX = remainsX - deltaX;
        element.style.left = currentX + "px";

        if (remainsY === 0 && remainsX === 0) clearInterval(timerId);
    }, delay);
};

/**
 * register用のコールバック関数
 */
Obm.registerAPICallBack = function(json){
    Obm.APIConnector.singleton().requestForBookmarks();
};

/**
 * get_tags用のコールバック関数
 */
Obm.getTagsAPICallBack = function(json){
    Obm.APIConnector.singleton().tags(json);
    Obm.TopPage.singleton().update(json);
};

/**
 * get_bookmarks用のコールバック関数
 */
Obm.getBookmarksAPICallBack = function(json){
    Obm.APIConnector.singleton().bookmarks(json);
    Obm.BookmarksPage.singleton().update(json);
};

/**
 * check_registered用のコールバック関数
 */
Obm.checkRegisteredAPICallBack = function(json){
    Obm.RegisterPage.singleton().update(json);
    Obm.TopPage.singleton().hide();
    Obm.RegisterPage.singleton().show();
};

/**
 * delete用のコールバック関数
 */
Obm.deleteAPICallBack = function(json){
    Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {api_name: "get_tags"});
    Obm.APIConnector.singleton().requestForBookmarks({tags: [], keywords: "", page_count: "1"});
};
// }}}

// {{{ Obm.Box
/**
 * ボックスクラス
 */
Obm.Box = function(){

    /** パーツ用情報リスト */
    this._props = {};

    /** パーツHTML要素コレクション */
    this._parts = {};

    /** 枠の要素 */
    this._frame = document.createElement("div");

    this.hide();
};
Obm.Box.prototype = {
    show: function(){
        this._frame.style.display = "inline";
    },
    hide: function(){
        this._frame.style.display = "none";
    },
    generate: function(){
        for (var i in this._props) this.createPart(i);
    },
    update: function(data){
        for (var i in this._props) this.updatePart(i, data);
    },
    /**
     * パーツ用情報を準備する
     *
     * @param str name  パーツ名
     * @param obj props
     *              id       : パーツ用のdiv要素オブジェクトへ付すid
     *              className: パーツ用のdiv要素オブジェクトへ付すCSSクラス名
     *              template : コンテンツを生成するための関数を設定する。
     *                         第1引数に、パーツ用のdiv要素オブジェクト、
     *                         第2引数に、updateに渡すための変数格納用リスト、
     *              update   : templateの内容を更新用データにより更新するための関数を設定する。
     *                         第1引数に、templateから受け取った変数リスト、
     *                         第2引数に、更新用データ、
     */
    preparePart: function(name, props){
        this._props[name] = props;
    },
    /**
     * パーツを生成する
     */
    createPart: function(name){

        // HTML要素の生成
        this._parts[name] = document.createElement("div");
        if ("id" in this._props[name])
            this._parts[name].id = this._props[name].id;
        if ("className" in this._props[name])
            this._parts[name].className = this._props[name].className;
        this._frame.appendChild(this._parts[name]);

        // 内容の生成
        this._props[name]._variables = {};
        if ("template" in this._props[name]) {
            if (typeof(this._props[name].template) === "string") {
                this._parts[name].innerHTML = this._props[name].template;
            } else {
                this._props[name].template.call(this, this._parts[name], this._props[name]._variables);
            };
        };
    },
    /**
     * パーツを更新する
     *
     * @param mixed data 再表示用の情報、詳細はupdate参照
     */
    updatePart: function(name, data){
        if (name in this._props && "update" in this._props[name])
            this._props[name].update.call(this, this._props[name]._variables, data);
    },
    isDisplayed: function(){
        if (this._frame.style.display === "inline") return true;
        return false;
    },
    part: function(name){
        return this._parts[name];
    },
    frame: function(){
        return this._frame;
    }
};
// }}}

// {{{ Obm.TopPage
/**
 * トップページクラス
 */
Obm.TopPage = function(){

    this._frame.className = "top_page";

    this._keywordField = null;

    this.preparePart("body", {
        className: "top_page-body",
        template: function(element, vars){
            var self = this;

            var tags = document.createElement("div");
            tags.className = "top_page-body-tags";
            element.appendChild(tags);
            vars.tags = function(data){

                tags.innerHTML = "";
                for (var i = 0; i < data.length; i++) {

                    var a = document.createElement("a");
                    a.className = self.getTagClassName(data[i]);
                    a.href = "#";
                    a.innerHTML = Obm.escapeHTML(data[i]);
                    (function(a, tag){
                        Obm.observeEvent(a, "click", function(){
                            self.updateTagCondition(tag);
                            Obm.APIConnector.singleton().requestForBookmarks({keywords: keyword.value, page_count: "1"});
                            Obm.BookmarksPage.singleton().show();
                            a.className = self.getTagClassName(tag);
                        });
                    })(a, data[i]);
                    tags.appendChild(a);
                    tags.appendChild(document.createTextNode(" "));
                };
            };

            var keyword = document.createElement("input");
            keyword.className = "top_page-keyword";
            keyword.type = "text";
            element.appendChild(keyword);
            this._keywordField = keyword;

            var search = document.createElement("input");
            search.className = "top_page-search";
            search.type = "button";
            search.value = "Go!";
            Obm.observeEvent(search, "click", function(){
                Obm.APIConnector.singleton().requestForBookmarks({keywords: keyword.value, page_count: "1"});
                Obm.BookmarksPage.singleton().show();
            });
            element.appendChild(search);

            var reg = document.createElement("input");
            reg.className = "top_page-register";
            reg.type = "button";
            reg.value = "Register";
            Obm.observeEvent(reg, "click", function(){
                Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {
                    api_name: "check_registered",
                    url: encodeURIComponent(Obm.requestParams.url)
                });
            });
            element.appendChild(reg);
        },
        update: function(vars, data){
            vars.tags(data);
        }
    });
};
Obm.TopPage = Obm.extend(Obm.TopPage, {
    applyNew: Obm.makeApplyNew(),
    singleton: Obm.makeSingleton()
});
Obm.TopPage.prototype = Obm.extend(new Obm.Box, {
    show: function(){
        var self = this;
        setTimeout(function(){
            self._keywordField.focus();
        }, 50);
        this._frame.style.display = "inline";
    },
    updateTagCondition: function(tag){
        var conds = Obm.APIConnector.singleton().conditions();
        // 既に同名タグがあれば削除し、無ければ加える
        if (Obm.inArray(tag, conds.tags)) {
            conds.tags = (function(){
                var deleted = [];
                for (var i = 0; i < conds.tags.length; i++) {
                    if (tag !== conds.tags[i]) deleted.push(conds.tags[i]);
                };
                return deleted;
            })();
        } else {
            conds.tags.push(tag);
        };
    },
    getTagClassName: function(tag){
        if (Obm.inArray(tag, Obm.APIConnector.singleton().conditions().tags)) {
            return "clickable top_page-tag top_page-tag_selected";
        } else {
            return "clickable top_page-tag";
        };
    },
    tags: function(tags){
        if (tags !== undefined) this._tags = tags;
        return this._tags;
    }
});
// }}}

// {{{ Obm.RegisterPage
/**
 * 登録ページクラス
 */
Obm.RegisterPage = function(){

    this._frame.className = "register_page";

    // 入力時のタグ文字列分離文字
    this._tagSeparator = ",";

    // サーバ送信時のタグ文字列分離文字
    this._serverTagSeparator = "\t";

    this._tagsField = null;

    this.preparePart("body", {
        className: "register_page-body",
        template: function(element, vars){
            var self = this;

            var titleLabel = document.createElement("span");
            titleLabel.className = "register_page-title_label";
            titleLabel.innerHTML = "- title -";
            element.appendChild(titleLabel);

            var title = document.createElement("input");
            title.className = "register_page-title";
            title.type = "text";
            title.value = Obm.requestParams.title;
            element.appendChild(title);

            var tagsLabel = document.createElement("span");
            tagsLabel.className = "register_page-tags_label";
            tagsLabel.innerHTML = "- tags -";
            element.appendChild(tagsLabel);

            var sup = document.createElement("div");
            sup.className = "register_page-body-supplement";
            element.appendChild(sup);

            var tags = document.createElement("input");
            tags.className = "register_page-tags";
            tags.type = "text";
            Obm.observeEvent(tags, "keyup", function(){
                sup.innerHTML = "";
                var t = Obm.APIConnector.singleton().tags();
                var s = Obm.RegisterPage.singleton().tagSeparator();
                // 最後のタブ区切り文字以前を切り捨て、周辺の半角空白はトリムする
                var p = tags.value.replace(new RegExp('^.*' + s + '[ ]*', ""), "");
                if (p !== "") {
                    for (var i = 0; i < t.length; i++) {
                        // matchではなくindexOfなのは、メタ文字を単なる文字列として解釈してくれるため
                        if (t[i].indexOf(p, 0) !== -1) {
                            sup.innerHTML += t[i];
                            sup.innerHTML += "<br />";
                        };
                    };
                };
            });
            element.appendChild(tags);
            this._tagsField = tags;

            var commentLabel = document.createElement("span");
            commentLabel.className = "register_page-comment_label";
            commentLabel.innerHTML = "- comment -";
            element.appendChild(commentLabel);

            var comment = document.createElement("input");
            comment.className = "register_page-comment";
            comment.type = "text";
            element.appendChild(comment);

            var ok = document.createElement("input");
            ok.className = "register_page-ok";
            ok.type = "button";
            ok.value = "OK";
            Obm.observeEvent(ok, "click", function(){
                self.register(title.value, tags.value, comment.value);
                self.hide();
                setTimeout(function(){
                    Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {api_name: "get_tags"});
                    Obm.TopPage.singleton().show();
                }, 200);
            });
            element.appendChild(ok);
            vars.ok = function(data){
                if (data.registered === true) {
                    ok.disabled = true;
                    return;
                };
                ok.disabled = false;
            };

            var cansel = document.createElement("input");
            cansel.className = "register_page-cansel";
            cansel.type = "button";
            cansel.value = "Cancel";
            Obm.observeEvent(cansel, "click", function(){
                self.hide();
                Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {api_name: "get_tags"});
                Obm.TopPage.singleton().show();
            });
            element.appendChild(cansel);

            var notice = document.createElement("span");
            notice.className = "register_page-notice";
            notice.innerHTML = "This site has been registered!";
            notice.style.display = "none";
            element.appendChild(notice);
            vars.notice = function(data){
                if (data.registered === true) {
                    notice.style.display = "inline";
                    return;
                };
                notice.style.display = "none";
            };
        },
        update: function(vars, data){
            vars.ok(data);
            vars.notice(data);
        }
    });
};
Obm.RegisterPage = Obm.extend(Obm.RegisterPage, {
    applyNew: Obm.makeApplyNew(),
    singleton: Obm.makeSingleton()
});
Obm.RegisterPage.prototype = Obm.extend(new Obm.Box, {
    show: function(){
        var self = this;
        setTimeout(function(){
            self._tagsField.focus();
        }, 50);
        this._frame.style.display = "inline";
    },
    register: function(title, tags, comment){
        Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {
            api_name: "register",
            url: encodeURIComponent(Obm.requestParams.url),
            title: encodeURIComponent(title),
            tags: encodeURIComponent(this.parseTagsString(tags)),
            comment: encodeURIComponent(comment)
        });
    },
    parseTagsString: function(tagsString){
        var p = new RegExp(this._tagSeparator, "g");
        return tagsString.replace(p, this._serverTagSeparator);
    },
    tagSeparator: function(){
        return this._tagSeparator;
    },
    serverTagSeparator: function(){
        return this._serverTagSeparator;
    }
});
// }}}

// {{{ Obm.BookmarksPage
/**
 * ブックマーク一覧ページクラス
 */
Obm.BookmarksPage = function(){

    this._frame.className = "bookmarks_page";

    this.preparePart("body", {
        className: "bookmarks_page-body",
        template: function(element, vars){
            var self = this;

            var pframe = document.createElement("div");
            pframe.className = "bookmarks_page-body-paging";
            element.appendChild(pframe);

            var prev = document.createElement("a");
            prev.className = "clickable bookmarks_page-prev";
            prev.href = "#";
            prev.innerHTML = "[PREV]";
            Obm.observeEvent(prev, "click", function(){
                var prev = Obm.APIConnector.singleton().bookmarks().paging.previous_page_count;
                if (prev === null) return;
                Obm.APIConnector.singleton().requestForBookmarks({"page_count": prev});
            });
            pframe.appendChild(prev);

            var next = document.createElement("a");
            next.className = "clickable bookmarks_page-next";
            next.href = "#";
            next.innerHTML = "[NEXT]";
            Obm.observeEvent(next, "click", function(){
                var next = Obm.APIConnector.singleton().bookmarks().paging.next_page_count;
                if (next === null) return;
                Obm.APIConnector.singleton().requestForBookmarks({"page_count": next});
            });
            pframe.appendChild(next);

            var page = document.createElement("span");
            page.className = "bookmarks_page-page_count";
            pframe.appendChild(page);
            vars.page = function(data){
                page.innerHTML = "[" + data.paging.page_count + "/" + data.paging.total_page_count + "]";
            };

            var bookmarks = document.createElement("ul");
            bookmarks.className = "bookmarks_page-body-bookmarks";
            element.appendChild(bookmarks);
            vars.bookmarks = function(data){
                bookmarks.innerHTML = "";
                for (var i = 0; i < data.bookmarks.length; i++) {

                    var li = document.createElement("li");

                    var a = document.createElement("a");
                    a.className = "clickable title";
                    a.innerHTML = Obm.escapeHTML(data.bookmarks[i].title);
                    a.href = Obm.escapeHTML(data.bookmarks[i].url);
                    a.target = "_blank";
                    li.appendChild(a);
                    li.appendChild(document.createTextNode(" "));

                    var s = document.createElement("span");
                    s.innerHTML = Obm.escapeHTML(data.bookmarks[i].comment);
                    li.appendChild(s);
                    li.appendChild(document.createTextNode(" "));

                    var del = document.createElement("a");
                    del.className = "clickable delete";
                    del.innerHTML = "[delete]";
                    del.href = "#";
                    (function(url){
                        Obm.observeEvent(del, "click", function(){
                            if (confirm("OK?"))
                                Obm.APIConnector.singleton().requestByScriptTag(Obm.API_URL, {
                                    api_name: "delete",
                                    url: encodeURIComponent(url)
                                });
                        });
                    })(data.bookmarks[i].url);
                    li.appendChild(del);

                    bookmarks.appendChild(li);
                };
            };
        },
        update: function(vars, data){
            vars.page(data);
            vars.bookmarks(data);
        }
    });
};
Obm.BookmarksPage = Obm.extend(Obm.BookmarksPage, {
    applyNew: Obm.makeApplyNew(),
    singleton: Obm.makeSingleton()
});
Obm.BookmarksPage.prototype = Obm.extend(new Obm.Box, {
});
// }}}

// {{{ Obm.APIConnector
/**
 * API接続クラス
 */
Obm.APIConnector = function(){

    // APIへのログイン用パラメータ
    this._loginParams = {
         login_id: Obm.requestParams.login_id
        ,login_password: Obm.requestParams.login_password
    };

    // タグ情報リスト
    this._tags = {};

    // ブックマーク情報リスト
    this._bookmarks = {};

    // ブックマーク抽出用条件リスト
    this._conditions = {tags: [], keywords: [], page_count: "1", display_count: "10"};
};
Obm.APIConnector = Obm.extend(Obm.APIConnector, {
    applyNew: Obm.makeApplyNew(),
    singleton: Obm.makeSingleton(),
    createUrlByParameters: function(baseUrl, params){

        if (typeof(params) !== "object") var params = {};

        var sep = (baseUrl.match(/\?/) !== null) ? "&" : "?";
        var url = baseUrl + sep + "cache=" + (new Date).getTime();

        for (var i in params) {
            url += "&" + i + "=" + params[i];
        };
        return url;
    }
});
Obm.APIConnector.prototype = {
    /**
     * scriptタグを使ってAPIにリクエストを送信する
     *
     * @param str baseUrl
     * @param obj params
     */
    requestByScriptTag: function(baseUrl, params){
        params = Obm.extend(params || {}, this._loginParams);
        var url = Obm.APIConnector.createUrlByParameters(baseUrl, params);

        var s = document.createElement("script");
        s.charset = "UTF-8";
        s.src = url;
        document.body.appendChild(s);
    },
    extendConditions: function(conditions){
        return Obm.extend(this._conditions, conditions);
    },
    requestForBookmarks: function(conditions){
        var params = Obm.clone(this.extendConditions(conditions));

        // タグの配列を文字列に変換する
        params.tags = (function(){
            var tags = []
            for (var i = 0; i < params.tags.length; i++) {
                tags[i] = encodeURIComponent(params.tags[i]);
            };
            return tags.join(encodeURIComponent(Obm.RegisterPage.singleton().serverTagSeparator()));
        })();

        params.keywords = encodeURIComponent(params.keywords);

        this.requestByScriptTag(Obm.API_URL, Obm.extend(params, {
            api_name: "get_bookmarks"
        }));
    },
    tags: function(tags){
        if (tags !== undefined) this._tags = tags;
        return this._tags;
    },
    bookmarks: function(bookmarks){
        if (bookmarks !== undefined) this._bookmarks = bookmarks;
        return this._bookmarks;
    },
    conditions: function(){
        return this._conditions;
    }
};
// }}}

